#include "abstractlinegraphicitem.h"

#include "../../documents/data/graphicdata.h"
#include "../../undo/basicitemcommands.h"
#include "../../utils/undomanager.h"
#include "graphicitemcommon.h"

#include <ieditor.h>
#include <igraphicitemcontent.h>
#include <limits>

#include <QtCore/qmath.h>
#include <QtCore/QLineF>
#include <QtGui/QBrush>
#include <QtGui/QGraphicsBlurEffect>
#include <QtGui/QPainter>
#include <QtGui/QPen>
#include <QtGui/QStyleOptionGraphicsItem>
#include <QtGui/QTransform>

using namespace core;
using namespace gui;
using namespace swp;

AbstractLineGraphicItem::TransformationMatrix::TransformationMatrix(
    const QPainterPath &path, QGraphicsItem *parent) :
        LineGraphicItem(path, true, false, parent)
{

}

AbstractLineGraphicItem::ConnectionPoint::ConnectionPoint(
    qreal x, qreal y, int w, int h, uint index, bool isAnchor,
    const QPainterPath& shape, QGraphicsItem* item,
        const QList<QPointF>& points,
        AbstractLineGraphicItem* parent) :
        EllipseGraphicItem(QRectF(x, y, w, h), true, false, parent),
        m_index(index),
        m_isAnchor(isAnchor),
        m_shape(shape),
        m_item(item), // not used at the moment
        m_points(points), // not used at the moment
        m_parent(parent),
        m_lastPosition()
{
}

QVariant AbstractLineGraphicItem::ConnectionPoint::itemChange(
        GraphicsItemChange change, const QVariant &value)
{
    QVariant ret = QGraphicsEllipseItem::itemChange(change, value);

    QGraphicsScene* _scene = scene();

    if ((change == QGraphicsItem::ItemPositionChange ||
        change == QGraphicsItem::ItemPositionHasChanged) && _scene != 0)
    {
        if (m_parent != 0)
        {
            if (m_isAnchor)
            {
                m_parent->setAnchorPointSilent(m_index, scenePos());

                GraphicData* _scene = dynamic_cast<GraphicData* >(scene());

                if (_scene != 0 && _scene->snapToGridEnabled())
                {
                    const QPointF newPos = value.toPointF();
                    const QPointF adjustedPosition =
                        _scene->snapToGrid(this, pos(), newPos);

                    if (newPos != adjustedPosition)
                    {
                        ret = QVariant(adjustedPosition);
                    }

                    QPointF newScenePos = mapToScene(ret.toPointF());

                    if (newScenePos.x() < 0 || newScenePos.y() < 0)
                    {
                        ret = QVariant(mapFromScene(
                            newScenePos.x() < 0 ? 0 : newScenePos.x(),
                            newScenePos.y() < 0 ? 0 : newScenePos.y()));
                    }

                    if (_scene != 0)
                    {
                        _scene->expandIfNeeded(this, ret.toPointF());
                    }
                }
            }
            else
            {
                const QPointF pos = value.toPointF();
                const QPolygonF shape = m_shape.toFillPolygon();

                qreal distance = std::numeric_limits<qreal>::max();

                QPointF newPos = scenePos();

                for (int i = 0; i < shape.size() - 1; ++i)
                {
                    const int si = i + 1;

                    const QLineF edge(shape[i], shape[si]);

                    const QLineF horizontal(QPointF(0, pos.y()),
                        QPointF(_scene->width(), pos.y()));
                    const QLineF vertical(QPointF(pos.x(), 0),
                        QPointF(pos.x(), _scene->height()));

                    QPointF intersectionPoint;
                    QPointF tempPosH = scenePos();
                    QPointF tempPosV = scenePos();

                    bool intersected = false;

                    if (edge.intersect(horizontal, &intersectionPoint) ==
                        QLineF::BoundedIntersection)
                    {
                        tempPosH.ry() = intersectionPoint.y();
                        tempPosH.rx() = intersectionPoint.x();
                        intersected = true;
                    }

                    if (edge.intersect(vertical, &intersectionPoint) ==
                        QLineF::BoundedIntersection)
                    {
                        tempPosV.rx() = intersectionPoint.x();
                        tempPosV.ry() = intersectionPoint.y();
                        intersected = true;
                    }

                    const QPointF deltaH = tempPosH - pos;
                    const QPointF deltaV = tempPosV - pos;
                    const qreal tempDistance = qMin(deltaH.manhattanLength(),
                        deltaV.manhattanLength());

                    if (intersected && (tempDistance < distance))
                    {
                        distance = tempDistance;
                        newPos = deltaH.manhattanLength() <
                            deltaV.manhattanLength() ? tempPosH : tempPosV;
                    }
                }

                if (newPos != scenePos())
                {
                    if (m_index == 0)
                    {
                        m_parent->setStartPointSilent(newPos);
                    }
                    else
                    {
                        m_parent->setEndPointSilent(newPos);
                    }
                }

                ret = newPos;
            }
        }
    }

    if (!m_isAnchor && change == QGraphicsItem::ItemPositionHasChanged
        && m_parent != 0)
    {
        m_parent->notifyOfHandleChange(m_index == 0);
    }

    return ret;
}

void AbstractLineGraphicItem::ConnectionPoint::mousePressEvent(
    QGraphicsSceneMouseEvent* event)
{
    m_lastPosition = pos();

    QGraphicsEllipseItem::mousePressEvent(event);
}

void AbstractLineGraphicItem::ConnectionPoint::mouseReleaseEvent(
    QGraphicsSceneMouseEvent* event)
{
    QGraphicsEllipseItem::mouseReleaseEvent(event);

    if (pos() != m_lastPosition)
    {
        GraphicData* _scene = dynamic_cast<GraphicData* >(scene());

        if (_scene != 0)
        {
            QUndoCommand* command = m_isAnchor ?
                static_cast<QUndoCommand* >(new AnchorMoveCommand(
                    this, m_lastPosition, pos())) :
                static_cast<QUndoCommand* >(new ConnectPointMoveCommand(
                    this, m_lastPosition, pos()));

            UndoManager::getInstance()->push(_scene, command);

            _scene->emitDataChanged();
        }
    }
}

int AbstractLineGraphicItem::ConnectionPoint::type() const
{
    return CONNECTOR_HANDLE_ITEM;
}

AbstractLineGraphicItem::Label::Label(const QString& text,
    AbstractLineGraphicItem* parent) : RectGraphicItem(
        0, true, false, parent),
        m_textItem(0)
{
    m_textItem = new QGraphicsSimpleTextItem(text, this);

    m_textItem->setPos(PREFERRED_CONTENT_PADDING_SMALL,
        PREFERRED_CONTENT_PADDING_SMALL);

    /*setFlags(flags() | QGraphicsItem::ItemIsMovable |
        QGraphicsItem::ItemIsSelectable);*/
    setBrush(Qt::white);
    setPen(QPen(Qt::gray));

    relayout();
}

QString AbstractLineGraphicItem::Label::text() const
{
    return m_textItem->text();
}

void AbstractLineGraphicItem::Label::setText(const QString& text)
{
    m_textItem->setText(text);

    relayout();
}

void AbstractLineGraphicItem::Label::relayout()
{
    const QRectF childrenRect = childrenBoundingRect();

    setRect(QRectF(0, 0,
        childrenRect.width() + PREFERRED_CONTENT_PADDING_SMALL * 2,
        childrenRect.height() + PREFERRED_CONTENT_PADDING_SMALL * 2));
}

AbstractLineGraphicItem::AbstractLineGraphicItem(
    const QPointF& startPoint,
    const QPointF& endPoint,
    const QList<QPointF>& anchors,
    QGraphicsItem* parent) : LineGraphicItem(true, false, parent),
        IGraphicEditorItem(),
        m_id(QUuid::createUuid()),
        m_line(),
        m_content(0),
        m_thumbnail(PREFERRED_THUMBNAIL_SIZE, PREFERRED_THUMBNAIL_SIZE),
        m_startPoint(startPoint),
        m_endPoint(endPoint),
        m_anchorsPoints(anchors),
        m_selectionHighlight(0),
        m_transformMatrix(0),
        m_handles(),
        m_connections(),
        m_parentItem(0),
        m_documentData(0),
        m_startShape(),
        m_endShape(),
        m_startItem(0),
        m_endItem(0),
        m_startItemConnectPoints(),
        m_endItemConnectPoints(),
        m_palette(0),
        m_centerPoint(0),
        m_startArrow(0),
        m_endArrow(0),
        m_startArrowBoundingRect(),
        m_endArrowBoundingRect(),
        m_labels(),
        m_lineStyle(Qt::SolidLine),
        m_node(0),
        m_handlesMovable(true)
{
    createBasicLookAndFeel();
    //updateLine();
}

AbstractLineGraphicItem::~AbstractLineGraphicItem()
{

}

void AbstractLineGraphicItem::setLabel(LabelAnchor anchor,
    const QString& label)
{
    if (m_labels.contains(anchor))
    {
        Label* _label = m_labels[anchor];

        if (label.isEmpty())
        {
            delete _label;

            m_labels.remove(anchor);
        }
        else
        {
            _label->setText(label);
        }
    }
    else if (!label.isEmpty())
    {
        Label* _label = new Label(label, this);

        m_labels[anchor] = _label;
    }

    updateLine();
}

QString AbstractLineGraphicItem::label(LabelAnchor anchor) const
{
    QString ret;

    if (m_labels.contains(anchor))
    {
        ret = m_labels[anchor]->text();
    }

    return ret;
}

QPointF AbstractLineGraphicItem::startPoint() const
{
    return m_startPoint;
}

QPointF AbstractLineGraphicItem::endPoint() const
{
    return m_endPoint;
}

void AbstractLineGraphicItem::setStartPoint(const QPointF& startPoint)
{
    if (startPoint != m_startPoint)
    {
        m_startPoint = startPoint;
        updateLine();
    }
}

void AbstractLineGraphicItem::setEndPoint(const QPointF& endPoint)
{
    if (endPoint != m_endPoint)
    {
        m_endPoint = endPoint;
        updateLine();
    }
}

void AbstractLineGraphicItem::addAnchorPoint(const QPointF& anchor)
{
    m_anchorsPoints.append(anchor);

    updateLine();
}

void AbstractLineGraphicItem::setAnchorPoint(uint index,
    const QPointF& anchor)
{
    if (m_anchorsPoints.at(index) != anchor)
    {
        m_anchorsPoints[index] = anchor;

        updateLine();
    }
}

void AbstractLineGraphicItem::removeAnchorPoint(const QPointF& anchor)
{
    m_anchorsPoints.removeOne(anchor);

    updateLine();
}

void AbstractLineGraphicItem::removeAnchorPoints()
{
    m_anchorsPoints.clear();

    updateLine();
}

void AbstractLineGraphicItem::setStartHandleSnapping(QGraphicsItem* item,
    const QList<QPointF>& connectPoints)
{
    m_startItem = item;
    m_startItemConnectPoints = connectPoints;

    if (m_handles.size() >= 2)
    {
        m_handles.first()->m_item = item;
        m_handles.first()->m_points = connectPoints;
    }
}

void AbstractLineGraphicItem::setEndHandleSnapping(QGraphicsItem* item,
    const QList<QPointF>& connectPoints)
{
    m_endItem = item;
    m_endItemConnectPoints = connectPoints;

    if (m_handles.size() >= 2)
    {
        m_handles[1]->m_item = item;
        m_handles[1]->m_points = connectPoints;
    }
}

void AbstractLineGraphicItem::setStartShape(const QPainterPath& shape)
{
    m_startShape = shape;

    if (m_handles.size() >= 2)
    {
        m_handles.first()->m_shape = shape;
    }
}

void AbstractLineGraphicItem::setEndShape(const QPainterPath& shape)
{
    m_endShape = shape;

    if (m_handles.size() >= 2)
    {
        m_handles[1]->m_shape = shape;
    }
}

QList<QPointF> AbstractLineGraphicItem::anchors() const
{
    return m_anchorsPoints;
}

QPointF AbstractLineGraphicItem::center(
    QPair<QPointF, QPointF>* centerPoints) const
{
    QPointF ret;

    qreal length = 0;

    if (m_startPoint == m_endPoint)
    {
        ret = m_startPoint;

        if (centerPoints != 0)
        {
            centerPoints->first = m_startPoint;
            centerPoints->second = m_endPoint;
        }
    }
    else if (m_anchorsPoints.size() == 0)
    {
        const qreal dx = m_endPoint.x() - m_startPoint.x();
        const qreal dy = m_endPoint.y() - m_startPoint.y();

        length = qSqrt(qPow(dx, 2) + qPow(dy, 2));

        const qreal _length = length / 2;

        ret = QPointF(m_startPoint.x() + (dx * _length/length),
            m_startPoint.y() + (dy * _length/length));

        if (centerPoints != 0)
        {
            centerPoints->first = m_startPoint;
            centerPoints->second = m_endPoint;
        }
    }
    else
    {
        const int size = m_anchorsPoints.size() + 1;
        qreal* lengths = new qreal[size];

        qreal dx = m_anchorsPoints[0].x() - m_startPoint.x();
        qreal dy = m_anchorsPoints[0].y() - m_startPoint.y();

        length = qSqrt(qPow(dx, 2) + qPow(dy, 2));

        lengths[0] = length;

        for (int i = 1; i < m_anchorsPoints.size(); ++i)
        {
            dx = m_anchorsPoints[i].x() - m_anchorsPoints[i - 1].x();
            dy = m_anchorsPoints[i].y() - m_anchorsPoints[i - 1].y();

            const int _length = qSqrt(qPow(dx, 2) + qPow(dy, 2));
            lengths[i] = _length;

            length += _length;
        }

        const int index = m_anchorsPoints.size() - 1;

        dx = m_endPoint.x() - m_anchorsPoints[index].x();
        dy = m_endPoint.y() - m_anchorsPoints[index].y();

        const qreal _length = qSqrt(qPow(dx, 2) + qPow(dy, 2));
        lengths[m_anchorsPoints.size()] = _length;

        length += _length;
        length /= 2;

        int lineIndex = -1;

        for (int i = 0; i < size; ++i)
        {
            if (length - lengths[i] > 0)
            {
                length -= lengths[i];
            }
            else
            {
                lineIndex = i;
                break;
            }
        }

        qreal startX;
        qreal startY;

        if (lineIndex == 0)
        {
            startX = m_startPoint.x();
            startY = m_startPoint.y();

            dx = m_anchorsPoints[0].x() - startX;
            dy = m_anchorsPoints[0].y() - startY;

            if (centerPoints != 0)
            {
                centerPoints->first = m_startPoint;
                centerPoints->second = m_anchorsPoints[0];
            }
        }
        else if (lineIndex == m_anchorsPoints.size())
        {
            startX = m_anchorsPoints.last().x();
            startY = m_anchorsPoints.last().y();

            dx = m_endPoint.x() - startX;
            dy = m_endPoint.y() - startY;

            if (centerPoints != 0)
            {
                centerPoints->first = m_anchorsPoints.last();
                centerPoints->second = m_endPoint;
            }
        }
        else
        {
            startX = m_anchorsPoints[lineIndex - 1].x();
            startY = m_anchorsPoints[lineIndex - 1].y();

            dx = m_anchorsPoints[lineIndex].x() - startX;
            dy = m_anchorsPoints[lineIndex].y() - startY;

            if (centerPoints != 0)
            {
                centerPoints->first = m_anchorsPoints[lineIndex - 1];
                centerPoints->second = m_anchorsPoints[lineIndex];
            }
        }

        ret = QPointF(startX + (dx * length/lengths[lineIndex]),
            startY + (dy * length/lengths[lineIndex]));

        delete[] lengths;
    }

    return ret;
}

qreal AbstractLineGraphicItem::startAngle(
    QPair<qreal, qreal>* vector) const
{
    qreal ret = 0;

    const QPointF end = m_anchorsPoints.size() == 0 ? m_endPoint :
        m_anchorsPoints.first();

    ret = angle(QPair<QPointF, QPointF>(m_startPoint, end));

    if (vector != 0)
    {
        vector->first = end.x() - m_startPoint.x();
        vector->second = end.y() - m_startPoint.y();
    }

    return ret;
}

qreal AbstractLineGraphicItem::middleAngle(
    const QPair<QPointF, QPointF>& centerPoints,
    QPair<qreal, qreal>* vector) const
{
    qreal ret = angle(centerPoints);

    if (vector != 0)
    {
        vector->first = centerPoints.second.x() - centerPoints.first.x();
        vector->second = centerPoints.second.y() - centerPoints.first.y();
    }

    return ret;
}

qreal AbstractLineGraphicItem::endAngle(QPair<qreal, qreal>* vector) const
{
    qreal ret = 0;

    const QPointF start = m_anchorsPoints.size() == 0 ? m_startPoint :
        m_anchorsPoints.last();

    ret = angle(QPair<QPointF, QPointF>(start, m_endPoint));

    if (vector != 0)
    {
        vector->first = m_endPoint.x() - start.x();
        vector->second = m_endPoint.y() - start.y();
    }

    return ret;
}

QPixmap AbstractLineGraphicItem::thumbnail(core::ThumbnailRole role,
    int size) const
{
    const int _size = size == -1 ? PREFERRED_THUMBNAIL_SIZE : size;

    m_thumbnail = QPixmap(_size, _size);
    m_thumbnail.fill(Qt::transparent);
    QPainter painter(&m_thumbnail);
    if (antialiasing())
    {
        painter.setRenderHint(QPainter::Antialiasing);
    }

    QRectF thumbnailRect = QRectF(0, 0,
        _size - PREFERRED_THUMBNAIL_PADDING -
        PREFERRED_THUMBNAIL_PEN_SIZE,
        _size - PREFERRED_THUMBNAIL_PADDING -
        PREFERRED_THUMBNAIL_PEN_SIZE);

    const qreal coefficient =  qMin(
        thumbnailRect.width() / m_line.boundingRect().width(),
        thumbnailRect.height() / m_line.boundingRect().height());

    QTransform transform;
    transform.scale(coefficient, coefficient);
    QPainterPath transformed = transform.map(m_line);
    QLineF transformedHelper = transform.map(
        QLineF(m_startPoint, m_endPoint));
    transformedHelper.translate(
        (_size - transformed.boundingRect().width()) / 2,
        (_size - transformed.boundingRect().height()) / 2);

    QPainterPath _transformed = transformed.translated(
        (_size - transformed.boundingRect().width()) / 2,
        (_size - transformed.boundingRect().height()) / 2);

    painter.setPen(QPen(Qt::transparent, 0));
    painter.setBrush(brush());
    painter.drawPath(_transformed);

    if (m_content != 0)
    {
        m_content->drawThumbnail(painter, _transformed.boundingRect());
    }

    painter.setBrush(QBrush(Qt::black));

    if (m_startArrow != 0)
    {
        const QPair<QPen, QBrush> palette = startArrowPalette();

        QPainterPath startArrowTransformed = transform.map(
            m_startArrow->path());

        startArrowTransformed.translate(transformedHelper.p1().x(),
            transformedHelper.p1().y() -
                startArrowTransformed.boundingRect().height() / 2);

        painter.setPen(palette.first);
        painter.setBrush(palette.second);
        painter.drawPath(startArrowTransformed);
    }

    if (m_endArrow != 0)
    {
        const QPair<QPen, QBrush> palette = endArrowPalette();

        QPainterPath endArrowTransformed = transform.map(
            m_endArrow->path());

        endArrowTransformed.translate(transformedHelper.p2().x() -
                endArrowTransformed.boundingRect().width() / 2,
            transformedHelper.p2().y());

        painter.setPen(palette.first);
        painter.setBrush(palette.second);
        painter.drawPath(endArrowTransformed);
    }

    return m_thumbnail;
}

void AbstractLineGraphicItem::editorModeChange()
{
    GraphicData* _scene = dynamic_cast<GraphicData* >(scene());

    if (_scene != 0)
    {
        switch (_scene->currentState())
        {
            case IEditor::SelectMove:
                if (m_transformMatrix != 0)
                {
                    _scene->removeItem(m_transformMatrix);
                    delete m_transformMatrix;
                    m_transformMatrix = 0;
                }
                break;
            case IEditor::Transform:
                if (m_transformMatrix == 0)
                {
                    m_transformMatrix = new TransformationMatrix(
                        m_line, this);
                    m_transformMatrix->setPen(QPen(Qt::transparent, 0));
                    m_transformMatrix->setBrush(QBrush(Qt::darkBlue));
                }
                break;
            case IEditor::Connect:
                break;
            case IEditor::Edit:
                break;
            default:
                Q_ASSERT(false); // shouldn't ever happen
        }
    }
}

void AbstractLineGraphicItem::setContent(core::IGraphicItemContent* content)
{

}

IGraphicItemContent* AbstractLineGraphicItem::content() const
{
    return 0;
}

void AbstractLineGraphicItem::setPalette(
    const core::Palette* const palette)
{
    m_palette = palette;

    refreshPalette();
}

int AbstractLineGraphicItem::paletteType() const
{
    return Whole;
}

void AbstractLineGraphicItem::scaleToFitContent()
{

}

void AbstractLineGraphicItem::handleContentRefresh()
{

}

void AbstractLineGraphicItem::handleConnectorPositionChange()
{

}

void AbstractLineGraphicItem::handleSelfConnect(
    IGraphicEditorItem* connector, const QPointF& cursorPos,
    bool previousSelfConnect)
{

}

void AbstractLineGraphicItem::ensureHandlesGeometryChanges(bool enabled)
{

}

QPointF AbstractLineGraphicItem::nearestConnectionPoint(
    const QPointF& to, ConnectionDirection direction) const
{
    QPointF ret = to;

    return ret;
}

void AbstractLineGraphicItem::handleSceneResize()
{

}

bool AbstractLineGraphicItem::supportsConnection(
    const ConnectionData& connection) const
{
    return false;
}

QList<ConnectionData> AbstractLineGraphicItem::connections() const
{
    QList<ConnectionData> ret;

    return ret;
}

void AbstractLineGraphicItem::connectionsRefreshed()
{
    Q_ASSERT(m_documentData != 0); // shouldn't be null

    GraphicData* graphicData = dynamic_cast<GraphicData*>(m_documentData);

    Q_ASSERT(graphicData != 0); // shouldn't be null

    m_connections = graphicData->connections(this);
}

IEditorItem* AbstractLineGraphicItem::parentEditorItem(
    IEditorItem* parent) const
{
    return m_parentItem;
}

void AbstractLineGraphicItem::setParentEditorItem(
    IEditorItem* parent)
{
    m_parentItem = parent;
}

IDocumentData* AbstractLineGraphicItem::parentDocumentData() const
{
    return m_documentData;
}

void AbstractLineGraphicItem::setParentDocumentData(IDocumentData* data)
{
    m_documentData = data;
}

bool AbstractLineGraphicItem::isModel() const
{
    return false;
}

bool AbstractLineGraphicItem::isGraphic() const
{
    return true;
}

bool AbstractLineGraphicItem::isText() const
{
    return false;
}

QUuid AbstractLineGraphicItem::id() const
{
    return m_id;
}

bool AbstractLineGraphicItem::doActionForProperty(
    const QString& property, const QVariantList& value,
    const core::SerializationEngine& helper)
{
    bool ret = true;

    if (property == "id")
    {
        m_id = QUuid(value.first().toString());
    }
    else if (property == "startPoint")
    {
        setStartPoint(value.first().toPointF());
    }
    else if (property == "anchors")
    {
        foreach (const QVariant& point, value)
        {
            addAnchorPoint(point.toPointF());
        }
    }
    else if (property == "endPoint")
    {
        setEndPoint(value.first().toPointF());
    }
    else if (property == "zvalue")
    {
        setZValue(value.first().toDouble());
    }

    return ret;
}

void AbstractLineGraphicItem::deserializationFinished(bool ok)
{
    updateHandles();
}

QHash<QString, QVariantList> AbstractLineGraphicItem::serializationData(
    const core::SerializationEngine& helper) const
{
    QHash<QString, QVariantList> ret;

    QVariantList list;

    list.append(QVariant(id().toString()));

    ret["id"] = list;

    list.clear();

    list.append(QVariant(m_startPoint));

    ret["startPoint"] = list;

    list.clear();

    if (m_anchorsPoints.size() > 0)
    {
        foreach (const QPointF& point, m_anchorsPoints)
        {
            list.append(QVariant(point));
        }

        ret["anchors"] = list;
    }

    list.clear();

    list.append(QVariant(m_endPoint));

    ret["endPoint"] = list;

    list.clear();

    list.append(QVariant(zValue()));

    ret["zvalue"] = list;

    return ret;
}

void AbstractLineGraphicItem::registerNode(core::Node* node)
{
    m_node = node;
}

const Node* const AbstractLineGraphicItem::node() const
{
    return m_node;
}

int AbstractLineGraphicItem::type() const
{
    return ABS_LINE_ITEM;
}

QVariant AbstractLineGraphicItem::itemChange(GraphicsItemChange change,
        const QVariant &value)
{
    if (change == QGraphicsItem::ItemSelectedHasChanged)
    {
        GraphicData* _scene = dynamic_cast<GraphicData* >(scene());

        if (value.toBool())
        {
            if (_scene != 0)
            {
                createHighlight();

                if (_scene->currentState() == IEditor::SelectMove)
                {
                    deleteTransformationMatrix();
                }
                else if (_scene->currentState() == IEditor::Transform)
                {
                    createTransformationMatrix();
                }
            }
        }
        else
        {
            deleteHighlight();
            deleteTransformationMatrix();
        }
    }

    return QGraphicsPathItem::itemChange(change, value);
}

void AbstractLineGraphicItem::setStartArrow(QGraphicsPathItem* item)
{
    if (m_startArrow != 0)
    {
        if (scene() != 0)
        {
            scene()->removeItem(m_startArrow);
        }

        delete m_startArrow;
    }

    const QPair<QPen, QBrush> palette = startArrowPalette();

    m_startArrowBoundingRect = item->boundingRect();

    m_startArrow = item;
    m_startArrow->setPen(palette.first);
    m_startArrow->setBrush(palette.second);
    m_startArrow->setParentItem(this);
}

void AbstractLineGraphicItem::setEndArrow(QGraphicsPathItem* item)
{
    if (m_endArrow != 0)
    {
        if (scene() != 0)
        {
            scene()->removeItem(m_endArrow);
        }

        delete m_endArrow;
    }

    const QPair<QPen, QBrush> palette = endArrowPalette();

    m_endArrowBoundingRect = item->boundingRect();

    m_endArrow = item;
    m_endArrow->setPen(palette.first);
    m_endArrow->setBrush(palette.second);
    m_endArrow->setParentItem(this);
}

QPair<QPen, QBrush> AbstractLineGraphicItem::startArrowPalette() const
{
    QPair<QPen, QBrush> ret;

    QPen pen(QColor(Qt::black));
    pen.setWidth(2);
    QBrush brush(QColor(Qt::white));

    ret.first = pen;
    ret.second = brush;

    return ret;
}

QPair<QPen, QBrush> AbstractLineGraphicItem::endArrowPalette() const
{
    QPair<QPen, QBrush> ret;

    QPen pen(QColor(Qt::black));
    pen.setWidth(2);
    QBrush brush(QColor(Qt::white));

    ret.first = pen;
    ret.second = brush;

    return ret;
}

void AbstractLineGraphicItem::setLineStyle(Qt::PenStyle style)
{
    m_lineStyle = style;
}

void AbstractLineGraphicItem::updateLine(bool updateHandles)
{
    getLine(2, m_line);
    setPath(m_line);

    if (m_selectionHighlight != 0)
    {
        m_selectionHighlight->setPath(m_line);
    }
    if (m_transformMatrix != 0)
    {
        m_transformMatrix->setPath(m_line);
    }

    if (m_centerPoint == 0)
    {
        m_centerPoint = new EllipseGraphicItem(
            QRectF(-4, -4, 8, 8), true, false, this);
        m_centerPoint->setPen(QPen(Qt::gray));
        m_centerPoint->setZValue(ABS_LINE_ZVALUE + 1);
    }

    QPair<QPointF, QPointF> centerPoints;
    const QPointF _center = center(&centerPoints);

    QPair<qreal, qreal> startVector;
    QPair<qreal, qreal> middleVector;
    QPair<qreal, qreal> endVector;

    const qreal _startAngle = startAngle(&startVector);
    const qreal _middleAngle = middleAngle(centerPoints, &middleVector);
    const qreal _endAngle = endAngle(&endVector);

    const int startQuadrant = quadrant(_startAngle);
    const int middleQuadrant = quadrant(_middleAngle);
    const int endQuadrant = quadrant(_endAngle);

    m_centerPoint->setPos(_center);

    if (m_startArrow != 0)
    {
        m_startArrow->setPos(arrowPos(m_startArrowBoundingRect,
            m_startPoint, _startAngle));
        m_startArrow->setRotation(-_startAngle);
    }

    if (m_endArrow != 0)
    {
        m_endArrow->setPos(arrowPos(m_endArrowBoundingRect,
            m_endPoint, _endAngle));
        m_endArrow->setRotation(-_endAngle);
    }

    foreach (LabelAnchor anchor, m_labels.keys())
    {
        Label* label = m_labels[anchor];
        label->setPos(labelPos(label->boundingRect(), anchor,
            startQuadrant, middleQuadrant, endQuadrant,
            startVector, middleVector, endVector, _center));
    }

    if (updateHandles)
    {
        this->updateHandles();
    }
}

void AbstractLineGraphicItem::setHandlesMovable(bool movable)
{
    m_handlesMovable = movable;
}

void AbstractLineGraphicItem::createBasicLookAndFeel()
{
    setPen(QPen(Qt::transparent, 0,
        Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    setBrush(QBrush(Qt::black));
    setZValue(ABS_LINE_ZVALUE);
}

void AbstractLineGraphicItem::createHighlight()
{
    if (m_selectionHighlight == 0)
    {
        QGraphicsBlurEffect* effect = new QGraphicsBlurEffect();
        effect->setBlurHints(QGraphicsBlurEffect::PerformanceHint);
        effect->setBlurRadius(6);

        QPainterPath path;
        getLine(4, path);

        m_selectionHighlight = new QGraphicsPathItem(path,
            this);
        m_selectionHighlight->setPen(QPen(Qt::transparent, 0));
        m_selectionHighlight->setBrush(QBrush(m_palette != 0 ?
            m_palette->selectionColor() : Qt::darkCyan));
        m_selectionHighlight->setGraphicsEffect(effect);
        m_selectionHighlight->setFlags(m_selectionHighlight->flags() |
            QGraphicsItem::ItemStacksBehindParent);
    }
}

void AbstractLineGraphicItem::createTransformationMatrix()
{
    if (m_transformMatrix == 0)
    {
        m_transformMatrix = new TransformationMatrix(
            m_line, this);
        m_transformMatrix->setPen(QPen(Qt::transparent, 0));
        m_transformMatrix->setBrush(QBrush(m_palette != 0 ?
            m_palette->transformationColor() : Qt::darkBlue));
    }
}

void AbstractLineGraphicItem::deleteHighlight()
{
    if (m_selectionHighlight != 0 && scene() != 0)
    {
        scene()->removeItem(m_selectionHighlight);
    }

    delete m_selectionHighlight;
    m_selectionHighlight = 0;
}

void AbstractLineGraphicItem::deleteTransformationMatrix()
{
    if (m_transformMatrix != 0 && scene() != 0)
    {
        scene()->removeItem(m_transformMatrix);
    }

    delete m_transformMatrix;
    m_transformMatrix = 0;
}

void AbstractLineGraphicItem::updateHandles()
{
    QGraphicsScene* _scene = scene();

    foreach (QGraphicsEllipseItem* item, m_handles)
    {
        if (_scene != 0)
        {
            _scene->removeItem(item);
        }
        delete item;
    }

    m_handles.clear();

    ConnectionPoint* startHandle = new ConnectionPoint(
        -4, -4, 8, 8, 0, false, m_startShape, m_startItem,
        m_startItemConnectPoints,  this);
    startHandle->setPos(m_startPoint.x(), m_startPoint.y());
    startHandle->setZValue(ABS_LINE_ZVALUE + 1);
    startHandle->setPen(QPen(Qt::black, 2));
    startHandle->setBrush(QBrush(Qt::white));

    m_handles.append(startHandle);

    ConnectionPoint* endHandle = new ConnectionPoint(
        -4, -4, 8, 8, 1, false, m_endShape, m_endItem,
        m_endItemConnectPoints, this);
    endHandle->setPos(m_endPoint.x(), m_endPoint.y());
    endHandle->setZValue(ABS_LINE_ZVALUE + 1);
    endHandle->setPen(QPen(Qt::black, 2));
    endHandle->setBrush(QBrush(Qt::white));

    m_handles.append(endHandle);

    uint index = 0;
    foreach (const QPointF& anchor, m_anchorsPoints)
    {
        ConnectionPoint* handle = new ConnectionPoint(
            -4, -4, 8, 8, index, true, QPainterPath(), 0,
            QList<QPointF>(), this);
        handle->setPos(anchor.x(), anchor.y());
        handle->setZValue(ABS_LINE_ZVALUE + 1);
        handle->setPen(QPen(Qt::black, 2));
        handle->setBrush(QBrush(Qt::yellow));

        m_handles.append(handle);
        ++index;
    }

    enableHandles();
}

void AbstractLineGraphicItem::getLine(int strokeWidth, QPainterPath& path)
{
    QPainterPath _path(m_startPoint);

    for (int i = 0; i < m_anchorsPoints.size(); ++i) {
        _path.lineTo(m_anchorsPoints[i]);
    }

    _path.lineTo(m_endPoint);

    QPainterPathStroker stroker;
    stroker.setDashPattern(m_lineStyle);
    stroker.setCapStyle(Qt::SquareCap);
    stroker.setJoinStyle(Qt::RoundJoin);
    stroker.setWidth(strokeWidth);

    path = stroker.createStroke(_path);
}

QPointF AbstractLineGraphicItem::arrowPos(const QRectF& itemBoudingRect,
    const QPointF& pos, qreal angle) const
{
    QPointF ret = pos;

    qreal _angle = angle;

    if (angle > 90 && angle < 180) // second quadrant
    {
        _angle = 180 - angle;
    }
    else if (angle > 180 && angle < 270) // third quadrant
    {
        _angle = angle - 180;
    }
    else if (angle > 270 && angle < 360) // fourth quadrant
    {
        _angle = 360 - angle;
    }

    qreal xshift = qAbs(qCos(_angle * (M_PI/180)) *
        (itemBoudingRect.height() / 2 + 4));
    if ((angle < 90 && angle > 0) || (angle < 360 && angle > 270) ||
        qAbs(angle - 0) < 0.0025 || qAbs(angle - 360) < 0.0025)
    {
        xshift *= -1;
    }
    else if (qAbs(angle - 90) < 0.0025 || qAbs(angle - 270) < 0.0025)
    {
        xshift = 0;
    }

    qreal yshift = qAbs(qSin(_angle * (M_PI/180)) *
        (itemBoudingRect.height() / 2 + 4));
    if (angle > 180 && angle < 360)
    {
        yshift *= -1;
    }
    else if (qAbs(angle - 0) < 0.0025 || qAbs(angle - 180) < 0.0025 ||
        qAbs(angle - 360) < 0.0025)
    {
        yshift = 0;
    }

    ret.rx() = pos.x() + xshift;
    ret.ry() = pos.y() + yshift;

    return ret;
}

QPointF AbstractLineGraphicItem::labelPos(const QRectF& itemBoudingRect,
    AbstractLineGraphicItem::LabelAnchor anchor,
    int startQuadrant, int middleQuadrant, int endQuadrant,
    const QPair<qreal, qreal>& startVector,
    const QPair<qreal, qreal>& middleVector,
    const QPair<qreal, qreal>& endVector,
    const QPointF& center) const
{
    QPointF ret;

    const qreal shiftLength = 20;

    int quadrant = 1;
    QPair<qreal, qreal> normalVector;

    qreal xcorrection = 0;
    qreal ycorrection = 0;

    switch (anchor)
    {
        case StartAbove:
        case StartBelow:
            ret = m_startPoint;
            quadrant = startQuadrant;
            normalVector.first = startVector.second;
            normalVector.second = startVector.first;

            xcorrection = itemBoudingRect.width();
            ycorrection = itemBoudingRect.height() / 2;

            break;
        case MiddleAbove:
        case MiddleBelow:
            ret = center;
            quadrant = middleQuadrant;
            normalVector.first = middleVector.second;
            normalVector.second = middleVector.first;

            xcorrection = itemBoudingRect.width() / 2;
            ycorrection = itemBoudingRect.height() / 2;

            break;
        case EndAbove:
        case EndBelow:
            ret = m_endPoint;
            quadrant = endQuadrant;
            normalVector.first = endVector.second;
            normalVector.second = endVector.first;

            ycorrection = itemBoudingRect.height() / 2;

            break;
        default:
            Q_ASSERT(false); // wrong mapping of anchor
            break;
    }

    if (quadrant == 2 || quadrant == 3)
    {
        if (anchor % 2 == 0) // above
        {
            normalVector.first *= -1;
        }
        else // below
        {
            normalVector.second *= -1;
        }
    }
    else
    {
        if (anchor % 2 == 0)
        {
            normalVector.second *= -1;
        }
        else
        {
            normalVector.first *= -1;
        }
    }

    qreal length = qSqrt(qPow(normalVector.first, 2) +
        qPow(normalVector.second, 2));
    if (length == 0)
    {
        length = shiftLength;
    }

    ret.rx() = (ret.x() + (normalVector.first * shiftLength/length))
        - xcorrection;
    ret.ry() = (ret.y() + (normalVector.second * shiftLength/length))
        - ycorrection;

    return ret;
}

qreal AbstractLineGraphicItem::angle(
    const QPair<QPointF, QPointF>& points) const
{
    qreal ret = 0;

    if (points.first.y() != points.second.y())
    {
        const qreal dx = points.second.x() - points.first.x();
        const qreal dy = points.second.y() - points.first.y();

        const qreal length = qSqrt(qPow(dx, 2) + qPow(dy, 2));

        const qreal ndx = 10;
        const qreal nlength = 10;

        ret = (dx * ndx) / (length * nlength); // ndx == nlength

        ret = qAcos(ret) * (180/M_PI);
    }
    else
    {
        ret = 0;
    }

    if (qAbs(ret - 0) < 0.0025 && points.first.x() > points.second.x())
    {
        ret = 180;
    }
    else if ((points.first.y() < points.second.y()) ||
        (points.first.y() - points.second.y() < 0.0025))
    {
        ret = 180 - ret + 180;
    }

    return ret;
}

int AbstractLineGraphicItem::quadrant(qreal angle) const
{
    int ret = 1;

    if (angle > 90 && (qAbs(angle - 180) < 0.0025 || angle < 180)) // second quadrant
    {
        ret = 2;
    }
    else if (angle > 180 && (qAbs(angle - 270) < 0.0025 || angle < 270)) // third quadrant
    {
        ret = 3;
    }
    else if (angle > 270 && (qAbs(angle - 360) < 0.0025 || angle < 360)) // fourth quadrant
    {
        ret = 4;
    }

    return ret;
}

void AbstractLineGraphicItem::setStartPointSilent(const QPointF& startPoint)
{
    if (startPoint != m_startPoint)
    {
        m_startPoint = startPoint;
        updateLine(false);
    }
}

void AbstractLineGraphicItem::setEndPointSilent(const QPointF& endPoint)
{
    if (endPoint != m_endPoint)
    {
        m_endPoint = endPoint;
        updateLine(false);
    }
}

void AbstractLineGraphicItem::setAnchorPointSilent(uint index,
    const QPointF& anchor)
{
    if (m_anchorsPoints.at(index) != anchor)
    {
        m_anchorsPoints[index] = anchor;

        updateLine(false);
    }
}

void AbstractLineGraphicItem::notifyOfHandleChange(bool startHandle)
{
    foreach (const ConnectionData& data, m_connections)
    {
        IGraphicEditorItem* item = dynamic_cast<IGraphicEditorItem*>(
            startHandle ? data.From : data.To);

        Q_ASSERT(item != 0); // type mismatch

        item->handleConnectorPositionChange();
    }
}

void AbstractLineGraphicItem::enableHandles()
{
    foreach (QGraphicsEllipseItem* handle, m_handles)
    {
        handle->setFlag(QGraphicsItem::ItemIsMovable, m_handlesMovable);
        handle->setFlag(QGraphicsItem::ItemSendsGeometryChanges,
            m_handlesMovable);
    }
}

void AbstractLineGraphicItem::refreshPalette()
{
    if (m_palette != 0)
    {
        setBrush(QBrush(m_palette->borderColor()));

        if (m_transformMatrix != 0)
        {
            m_transformMatrix->setBrush(QBrush(
                m_palette->transformationColor()));
        }

        if (m_selectionHighlight != 0)
        {
            m_selectionHighlight->setBrush(QBrush(
                m_palette->selectionColor()));
        }
    }
}
